package org.fjsei.yewu.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**必须配套有 redis 存储服务。
 * 双token机制下的： Refresh Token管理服务： 轮换验证，安全机制；
 * 负责在Redis中存储、验证和轮换refresh token
 */
@Slf4j
@Service
public class RefreshTokenService {
    private static final String REFRESH_TOKEN_PREFIX = "refresh_token:";
    //注意命名冲突：共享的nextjs服务器已经使用了 CACHE_PREFIX = "user_info:"
    private static final String USER_DEVICES_PREFIX = "user_devices:";
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Value("${jwt.refresh-token-validity-in-seconds:1296000}")
    private long refreshTokenValidityInSeconds;

    /**
     * 生成并存储新的refresh token
     */
    public String createRefreshToken(String username, DeviceInfo deviceInfo, long timeLeft) {
        long timeToLive= timeLeft>=0? timeLeft : refreshTokenValidityInSeconds;
        //生成唯一的token ID == 前端看见的token =实际 128位数的随机数
        String tokenId = UUID.randomUUID().toString();
        String tokenKey = REFRESH_TOKEN_PREFIX + tokenId;
        // 创建token实体
        RefreshTokenEntity tokenEntity = RefreshTokenEntity.builder()
                .token(tokenId)
                .username(username)
                .deviceInfo(deviceInfo)
                .createdAt(LocalDateTime.now())
                .expiresAt(LocalDateTime.now().plusSeconds(timeToLive))
                .used(false)
                .version(1)
                .build();
        // 存储到Redis，设置过期时间
        redisTemplate.opsForValue().set(
                tokenKey,
                tokenEntity,
                timeToLive,
                TimeUnit.SECONDS
        );

        // 将设备添加到用户的设备列表
        String userDevicesKey = USER_DEVICES_PREFIX + username;
        redisTemplate.opsForSet().add(userDevicesKey, deviceInfo.getDeviceId() + ":" + tokenId);
        //等价于重置了 新的refresh_token过期时间；
        redisTemplate.expire(userDevicesKey, timeToLive, TimeUnit.SECONDS);
        log.info("创建refresh token: user={}, device={}, tokenId={}",
                username, deviceInfo.getDeviceId(), tokenId);
        return tokenId;
    }

    /**
     * 验证refresh token并返回token实体
     */
    public RefreshTokenEntity validateRefreshToken(String tokenId) {
        String tokenKey = REFRESH_TOKEN_PREFIX + tokenId;
        RefreshTokenEntity tokenEntity = (RefreshTokenEntity) redisTemplate.opsForValue().get(tokenKey);
        if (tokenEntity == null) {
            log.warn("Refresh token不存在或已过期: {}", tokenId);
            return null;
        }
        if (tokenEntity.isUsed()) {
            log.warn("检测到refresh token重放攻击: user={}, device={}, tokenId={}",
                    tokenEntity.getUsername(),
                    tokenEntity.getDeviceInfo().getDeviceId(),
                    tokenId);
            //【被别人率先使用的refreshToken情况下】 检测到相同的，=重放攻击，撤销该用户该设备的所有token
            revokeDeviceTokens(tokenEntity.getUsername(), tokenEntity.getDeviceInfo().getDeviceId());
            return null;
        }
        if (LocalDateTime.now().isAfter(tokenEntity.getExpiresAt())) {
            log.warn("Refresh token已过期: {}", tokenId);
            deleteRefreshToken(tokenId);
            return null;
        }
        return tokenEntity;
    }

    /**
     * 不做登录直接刷新 轮换refresh token - 标记旧token为已使用，生成新token
     */
    public String rotateRefreshToken(String oldTokenId, RefreshTokenEntity oldToken, DeviceInfo deviceInfo) {
        if (oldToken == null) {
            return null;
        }
        // 计算剩余时间（秒）
        long timeLeft = calculateTimeLeft(oldToken);
        if (timeLeft < 0) {
            log.info("Refresh token已过期，无法轮换: {}", oldTokenId);
            return null;
        }
        // 标记旧token为已使用
        String oldTokenKey = REFRESH_TOKEN_PREFIX + oldTokenId;
        oldToken.setUsed(true);
        redisTemplate.opsForValue().set(
                oldTokenKey,
                oldToken,
                1,
                TimeUnit.HOURS  // 保留1小时用于检测重放攻击，【因此必须要】保证accessToken有效时间较短，不超过1小时的！
        );
        // 从用户设备列表中移除旧token
        String userDevicesKey = USER_DEVICES_PREFIX + oldToken.getUsername();
        redisTemplate.opsForSet().remove(userDevicesKey,
                oldToken.getDeviceInfo().getDeviceId() + ":" + oldTokenId);
        // 更新设备最后活跃时间
        deviceInfo.setLastActiveTime(System.currentTimeMillis());
        // 生成新token
        String newTokenId = createRefreshToken(oldToken.getUsername(), deviceInfo, timeLeft);
        log.info("轮换refresh token: user={}, device={}, oldToken={}, newToken={}, timeLeft={}s",
                oldToken.getUsername(),
                deviceInfo.getDeviceId(),
                oldTokenId,
                newTokenId,
                timeLeft);
        return newTokenId;
    }
    /**
     * 计算token剩余有效时间（秒）
     */
    private long calculateTimeLeft(RefreshTokenEntity token) {
        if (token == null || token.getExpiresAt() == null) {
            return -1;
        }
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime expiresAt = token.getExpiresAt();
        // 如果已经过期，返回负数
        if (now.isAfter(expiresAt)) {
            return -1;
        }
        // 计算剩余秒数
        java.time.Duration duration = java.time.Duration.between(now, expiresAt);
        return duration.getSeconds();
    }

    /**
     * 删除refresh token
     */
    public void deleteRefreshToken(String tokenId) {
        String tokenKey = REFRESH_TOKEN_PREFIX + tokenId;
        RefreshTokenEntity tokenEntity = (RefreshTokenEntity) redisTemplate.opsForValue().get(tokenKey);
        if (tokenEntity != null) {
            // 从用户设备列表中移除
            String userDevicesKey = USER_DEVICES_PREFIX + tokenEntity.getUsername();
            redisTemplate.opsForSet().remove(userDevicesKey,
                    tokenEntity.getDeviceInfo().getDeviceId() + ":" + tokenId);
        }
        redisTemplate.delete(tokenKey);
        log.info("删除refresh token: {}", tokenId);
    }

    /**
     * 撤销用户所有设备的token（用于登出所有设备）
     */
    public void revokeAllUserTokens(String username) {
        String userDevicesKey = USER_DEVICES_PREFIX + username;
        Set<Object> deviceTokens = redisTemplate.opsForSet().members(userDevicesKey);
        if (deviceTokens != null) {
            for (Object deviceToken : deviceTokens) {
                String[] parts = deviceToken.toString().split(":");
                if (parts.length == 2) {
                    String tokenId = parts[1];
                    deleteRefreshToken(tokenId);
                }
            }
        }
        redisTemplate.delete(userDevicesKey);
        log.info("撤销用户所有设备token: user={}", username);
    }

    /**被别人率先使用的refreshToken情况下撤销, 对方轮换刷新得到的最新refreshToken被作废。
     * 撤销用户+特定设备的 所有refreshToken, 但是对方已经拿的accessToken没变化依然可以用的。
     */
    public void revokeDeviceTokens(String username, String deviceId) {
        String userDevicesKey = USER_DEVICES_PREFIX + username;
        Set<Object> deviceTokens = redisTemplate.opsForSet().members(userDevicesKey);
        if (deviceTokens != null) {
            for (Object deviceToken : deviceTokens) {
                String[] parts = deviceToken.toString().split(":");
                if (parts.length == 2 && parts[0].equals(deviceId)) {
                    String tokenId = parts[1];
                    deleteRefreshToken(tokenId);
                }
            }
        }
        log.info("撤销用户设备token: user={}, device={}", username, deviceId);
    }

    /**
     * 获取用户所有活跃设备
     */
    public List<DeviceInfo> getUserActiveDevices(String username) {
        String userDevicesKey = USER_DEVICES_PREFIX + username;
        Set<Object> deviceTokens = redisTemplate.opsForSet().members(userDevicesKey);

        List<DeviceInfo> devices = new ArrayList<>();
        if (deviceTokens != null) {
            for (Object deviceToken : deviceTokens) {
                String[] parts = deviceToken.toString().split(":");
                if (parts.length == 2) {
                    String tokenId = parts[1];
                    String tokenKey = REFRESH_TOKEN_PREFIX + tokenId;
                    RefreshTokenEntity tokenEntity = (RefreshTokenEntity) redisTemplate.opsForValue().get(tokenKey);

                    if (tokenEntity != null && !tokenEntity.isUsed()) {
                        devices.add(tokenEntity.getDeviceInfo());
                    }
                }
            }
        }

        return devices;
    }
}
